%load_ext autoreload
%autoreload 2
import sys
from pathlib import Path
import numpy as np
import pandas as pd
import zfit_modelcore_cli as zfitcli
from zfit_plot import LogLogPlotDualAxisPlotly
The following code is used adapting the python script Zfit2.0, tuning it to be possible to call it from CLI (command line interface) and from python notebook to be launched also online platform. the following code line permit to run the original code available at https://exality.com/fitting-equivalent-circuits-to-impedance-data/
# !python Zfit.pyw
this example is useful in power electronic because is the typical output stage for a buck DC/DC stage.
Measuring it between the IN node and GND, then we can compare the meas. with the theoretical values and verify if there are discrepancies due to manufacturing.
# Test case
# DESCRIPTION: load example Zfit
# EXPECTED RESULT: fitting correct in plotly
filename_choke=r"./TestDataFiles/Sample ls(cpr).csv"
df_LCR = pd.read_csv(filename_choke)
#df_LCR.columns
range = zfitcli.Range()
ya = zfitcli.YAxes(2)
range.xa['Hz'] = df_LCR['FREQUENCY [Hz]']
ya.inputData[0] = df_LCR['MAG [Ω]']
ya.inputData[1] = df_LCR['PHASE [deg]']
my_model = zfitcli.DoModel()
fit_model = zfitcli.import_module("Models." + "ls(cpr)")
my_model.do_model_cli(range=range, ya=ya, model=fit_model)
From the graph below is visible that at low frequency the total impedance of the buck filter is just given by the load. This is why, in any PWM converter, the LC filter does not influence the DC transfer function.
After $f=300kHz$ the effect of the capacitor reduce the input impedance, till the resonance happen and the impedance start to be inductive again, increasing it with the frequency.
How the curve look like depends from the combination of $R, C, L$. If $L >> C$ then the curve will start to rise and then fall (exact the opposite fo this case)
newplot=LogLogPlotDualAxisPlotly()
newplot.add_trace(f=df_LCR['FREQUENCY [Hz]'],
zabs=df_LCR['MAG [Ω]'], zdeg=df_LCR['PHASE [deg]'],
label="Sample ls(cpr)")
newplot.add_trace(f=df_LCR['FREQUENCY [Hz]'],
zabs=my_model.ya.modeledData[0], zdeg=my_model.ya.modeledData[1],
label="FIT Sample ls(cpr)")
newplot.show()
This is the typical fitting necessary with any power inductor. the simplest model used by many manufacturer to take in cosideration the increase of the losses with the frequency is
The 4 components have different purpose:
RDC fits the DC losses, this can be measured to with a normal 4W method or letting flow some DC current and measuring the voltage across the choke
Rproximity is a resistance placed in the electrical model in parallel to the inductance to model the AC losses due to proximity effect, this is because the losses raise with $f^2$
L is the inductance measured at low frequency (this value reduce if the frequency increases due to the eddy currents)
C is the equivalent total capacitance seen at the terminal (this include intra-winding and terminal capacitance)
If we extract the real part and the imaginary part of the complex impedance we can see that look like the following:
the imaginary part (what normally in attributed just to the inductance) drop with the frequency, and the real part rise with $f^2$. It's also worth to mention that if we set $Rp=0$, the model comes back to the simple $RL$ series (the simples inductor model possible)
In the next section, we will fit the LCR measurements from 3 different inverter inductors, produced with different techniques:
in the next section we will fit the measurement of a Sirio inductor, used for a 3kW solar inverter.
# Test case
# DESCRIPTION: load coilcraft inductors and calc loss factor at different frequencies
# EXPECTED RESULT: new excel file with the same db and loss factor
filename_choke_sirio=r"./TestDataFiles/Inductor2.xls"
sheetname_choke_sirio="sirio"
# df_sirio = pd.read_excel(io=filename_choke, sheet_name=sheetname_choke_sirio)
df_sirio = pd.read_excel(io=filename_choke_sirio, sheet_name=sheetname_choke_sirio)
#R+j*X
f = df_sirio['Frequency (Hz)']
L = df_sirio['Inductance (H)']
R = df_sirio['Resistance (O)']
# we calc complex impedance abs(Z)*exp(j*deg)
Zabs_sirio = np.abs(R+1j*2*np.pi*f*L)
Zdeg_sirio = np.angle(R+1j*2*np.pi*f*L)
Fitting the measurements with a simple $RLC$ parallel we can obtain the following values:
# prepare input for the function
range.xa['Hz'] = f
ya.inputData[0] = Zabs_sirio
ya.inputData[1] = Zdeg_sirio
# do the fitting
my_model = zfitcli.DoModel()
fit_model = zfitcli.import_module("Models." + "rpcpl")
#fit_model = zfitcli.import_module("Models." + "rs(rpcpl)")
#fit_model = zfitcli.import_module("Models." + "rpcp(lsr_skin)")
#fit_model = zfitcli.import_module("Models." + "cpl_f")
# do_model_cli(self, range, ya, model, method_nr=0, do_norm_denorm=True):
my_model.do_model_cli(range=range, ya=ya, model=fit_model, method_nr=1)
# compare the real parts of the impedance
# the measured one
# the real measurements from sirio inductor
Z=Zabs_sirio*np.exp(1j*Zdeg_sirio)
ReZ_sirio=np.real(Z)
newplot=LogLogPlotDualAxisPlotly()
newplot.add_trace(f=f, zabs=ReZ_sirio, zdeg=None, label="meas_SirioInductor")
params={"Rs":0.015, "Rp":9108.42301217358, "L":0.00015606072332020876, "C":2.5235270544381366e-11}
fit_model = zfitcli.import_module("Models." + "rs(rpcpl)")
ReZ_fit = np.real(fit_model.model(w=2*np.pi*f, params=params))
newplot.add_trace(f=f, zabs=ReZ_fit, zdeg=None, label="fit_rpcpl_SirioInductor")
newplot.show()
As visible from the fitting above, the model cannot fit behaviors of real part lower than $f^2$. The fit will overestimate the losses for a large range of frequency. the same is visible in the magnitude/phase plot:
params={"Rs":0.015, "Rp":9108.42301217358, "L":0.00015606072332020876, "C":2.5235270544381366e-11}
fit_model = zfitcli.import_module("Models." + "rs(rpcpl)")
Z_fit = fit_model.model(w=2*np.pi*f, params=params)
newplot=LogLogPlotDualAxisPlotly()
newplot.add_trace(f=f, zabs=Zabs_sirio, zdeg=Zdeg_sirio*180/np.pi, label="SirioInductors")
newplot.add_trace(f=f, zabs=np.abs(Z_fit), zdeg=np.angle(Z_fit)/np.pi*180, label="FIT_SirioInductors")
newplot.show()
To improve this we can use the model that include a real part that increase with $f$.
# compare the real parts of the impedance
# the measured one
# the real measurements from sirio inductor
Z=Zabs_sirio*np.exp(1j*Zdeg_sirio)
ReZ_sirio=np.real(Z)
newplot=LogLogPlotDualAxisPlotly()
newplot.add_trace(f=f, zabs=ReZ_sirio, zdeg=None, label="meas_SirioInductor")
# # the fitted ones
# params={"Rldc":0.01, "L":4e-5, "C":1e-10, "sf":1e-6}
# # params={"Rldc":1e-2, "L":0.00015606072332020876, "C":2.5235270544381366e-11, "sf":1e-6}
# fit_model = zfitcli.import_module("Models." + "cpl_f")
# ReZ_fit = np.real(fit_model.model(w=2*np.pi*f, params=params))
# newplot.add_trace(f=f, zabs=ReZ_fit, zdeg=None, label="fit_cpl_f_SirioInductor")
# the fitted ones
params={"Rs":16.6434734, "L":4e-5, "C":1e-10, "sf":1e-6}
# params={"Rldc":1e-2, "L":0.00015606072332020876, "C":2.5235270544381366e-11, "sf":1e-6}
fit_model = zfitcli.import_module("Models." + "cpl_f")
ReZ_fit = np.real(fit_model.model(w=2*np.pi*f, params=params))
newplot.add_trace(f=f, zabs=ReZ_fit, zdeg=None, label="fit_cpl_f_SirioInductor")
newplot.show()
In the webinar from Omicron on 2020.07.27, they analyzed 4 different types of inductors:
SMD flat bad inductor
litz wire inductor
copper wire inductor
planar coils (PCB winding structure)
for more details: https://www.youtube.com/watch?v=T2OqewIUL3M&list=PLE3Pq-hBtiMRyV62bv2_UoKQ-YofQI5IB&index=4
.
What is interesting to compare is the real impedance of each type of inductor and see what are the best application for each inductor.
# load the data first from the Bode100 export
filename_chokes_webinar=r"./TestDataFiles/Inductors_2020-07-27T09_29_28.xlsx"
df_chokes = pd.read_excel(io=filename_chokes_webinar)
df_chokes.columns
f=df_chokes['Frequency (Hz)']
Zabs_flatwire=df_chokes['FlatBandWire: Trace 1: Magnitude (Ω)']
Zdeg_flatwire=df_chokes['FlatBandWire: Trace 2: Phase (°)']
Z_flatwire=Zabs_flatwire*np.exp(1j*Zdeg_flatwire*np.pi/180)
Zabs_planar=df_chokes['PCB LitzEmulation: Trace 1: Magnitude (Ω)']
Zdeg_planar=df_chokes['PCB LitzEmulation: Trace 2: Phase (°)']
Z_planar=Zabs_planar*np.exp(1j*Zdeg_planar*np.pi/180)
Zabs_RFlitz=df_chokes['RF Litz Wire: Trace 1: Magnitude (Ω)']
Zdeg_RFlitz=df_chokes['RF Litz Wire: Trace 2: Phase (°)']
Z_RFlitz=Zabs_RFlitz*np.exp(1j*Zdeg_RFlitz*np.pi/180)
Zabs_SolidStrand=df_chokes['SolidStrand: Trace 1: Magnitude (Ω)']
Zdeg_SolidStrand=df_chokes['SolidStrand: Trace 2: Phase (°)']
Z_SolidStrand=Zabs_SolidStrand*np.exp(1j*Zdeg_SolidStrand*np.pi/180)
It's interesting to see how different look like the behavior of the inductors, even if at $f=100kHz$ are identical. This is where FRA, like the Bode100 can make the difference, showing the behavior of the complex impedance of the inductors. some considerations we can do, already seeing the complex impedances:
newplot=LogLogPlotDualAxisPlotly()
newplot.add_trace(f=f, zabs=Zabs_flatwire, zdeg=Zdeg_flatwire, label="FlatBandWire")
newplot.add_trace(f=f, zabs=Zabs_planar, zdeg=Zdeg_planar, label="LitzEmulation")
newplot.add_trace(f=f, zabs=Zabs_RFlitz, zdeg=Zdeg_RFlitz, label="RF Litz Wire")
newplot.add_trace(f=f, zabs=Zabs_SolidStrand, zdeg=Zdeg_SolidStrand, label="SolidStrand")
newplot.show()
It's even more interesting to look at real part of the inductors. as visible from the following picture:
the Litz wire is the best until $f<577kHz$ then the PCB inductor wins. the behavior at high freq is worse than then other because present multiple resonances. At DC $f=0$ is the second best choice.
Solid strand is the best choice for EMC DM filter, because has the lowest losses in DC and the highest at the switching frequency. In case of high current is better than the flatwire, except for the higher frequency $f>7MHz$
newplot=LogLogPlotDualAxisPlotly()
newplot.add_trace(f=f, zabs=np.real(Z_flatwire), zdeg=None, label="FlatBandWire")
newplot.add_trace(f=f, zabs=np.real(Z_planar), zdeg=None, label="LitzEmulation")
newplot.add_trace(f=f, zabs=np.real(Z_RFlitz), zdeg=None, label="RF Litz Wire")
newplot.add_trace(f=f, zabs=np.real(Z_SolidStrand), zdeg=None, label="SolidStrand")
newplot.show()
range.xa['Hz'] = f
ya.inputData[0] = Zabs_flatwire
ya.inputData[1] = Zdeg_flatwire
my_model = zfitcli.DoModel()
fit_model = zfitcli.import_module("Models." + "rpcpl")
my_model.do_model_cli(range=range, ya=ya, model=fit_model)
newplot=LogLogPlotDualAxisPlotly()
newplot.add_trace(f=f, zabs=Zabs_flatwire, zdeg=Zdeg_flatwire, label="FlatBandWire")
newplot.add_trace(f=f, zabs=my_model.ya.modeledData[0], zdeg=my_model.ya.modeledData[1], label="FIT_FlatBandWire")
newplot.show()